把整套spring-cloud-dependencies依赖放在dependencyManagement里,可以在Maven Reopsitory网站查对应版本包,用哪个再去加哪个依赖
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
springboot 2.7用2021.0.5
Gateway工作流程的核心逻辑即 路由转发+执行过滤器链
加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
用yml配置
server:
port: 9527 #网关端口
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: t1 #路由id,没有规则但要求唯一,应配合服务名
uri: http://localhost:8080 #匹配后提供服务的路由地址
predicates:
- Path=/t/** #断言,路径相匹配的进行路由
DedupeResponseHeader=Vary Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_UNIQUE。此时走不走网关,都没有跨域问题跨域配置:
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
# 允许携带认证信息
# 允许跨域的源(网站域名/ip),设置*为全部
# 允许跨域请求里的head字段,设置*为全部
# 允许跨域的method, 默认为GET和OPTIONS,设置*为全部
# 跨域允许的有效期
allow-credentials: true
allowed-origins:
- "http://localhost:*"
allowed-headers: "*"
allowed-methods:
- OPTIONS
- GET
- POST
max-age: 3600
用bean配置
和yml配置选其一种即可
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder){
RouteLocatorBuilder.Builder routes = builder.routes();
routes.route("route_t",r -> r.path("/t").uri("http://localhost:8080/t")).build();
return routes.build();
}
默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
需要注册中心组件eureka
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
yml配置添加
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
yml中路由的uri换成匹配后提供服务的路由地址lb://serviceName,是gateway自动创建的负载均衡uri
匹配指定路径下的请求,可以是具体的请求,也可使用/**表示匹配所有子级请求
- Path=/user/**
匹配在指定日期时间之后发生的请求
- After=2020-02-05T15:10:03.685+08:00[Asia/Shanghai]
可以使用java类获取当前时间 2022-10-22T15:53:48.304+08:00[Asia/Shanghai]
ZonedDateTime now = ZonedDateTime.now();
匹配在指定日期时间之前发生的请求
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
匹配在指定日期时间之间发生的请求
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
需要两个参数,一个是 Cookie name ,一个是cookie的value的正则表达式。
路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行
- Cookie=cokieName, \d+
由两个参数组成,第一个参数为Header名称,第二参数为Header的Value值的正则表达式,表示:匹配指定名称且其值与正则表达式匹配的Header的请求
- Header=headerName, \d+
参数为请求的Host地址,多个参数使用逗号分割,设置的Host地址可以使用**表示通配符
- Host=**.somehost.org,**.anotherhost.org
配置中匹配的Host,可以匹配以somehost.org或者anotherhost.org结尾的Host地址,其他Host地址访问会出现错误。
由一个或多个HTTP Method组成,比如:POST、PUT、GET、DELETE
- Method=GET,POST
由两个参数组成,第一个参数为参数名称,第二参数为参数的值(满足正则即可),表示:匹配指定名称且其值与正则表达式匹配的带参的请求
- Query=name,\d+
参数由CIDR 表示法(IPv4 或 IPv6)字符串组成。配置中可以匹配IP为192.168.1.100的值,如果不满足192.168.1.1/24的IP规则,会出现错误。
- RemoteAddr=192.168.1.1/24
由group和weight(权重数值)组成,表示将相同的请求根据权重跳转到不同的uri地址,要求group的名称必须一致
- id: weight_high
uri: https://weighthigh.org
predicates:
- Weight=groupName, 8
- id: weight_low
uri: https://weightlow.org
predicates:
- Weight=groupName, 2
该路由会将约 80% 的流量转发到weighthigh.org,将约 20% 的流量转发到weightlow.org
生命周期
种类
GatewayFilter(局部过滤器):针对单个的服务路由
https://docs.spring.io/spring-cloud-gateway/docs/3.1.4/reference/html/#gatewayfilter-factories
GlobalFilter(全局过滤器):作用于所有路由信息
https://docs.spring.io/spring-cloud-gateway/docs/3.1.4/reference/html/#global-filters
继承GlobalFilter,Ordered接口
@Component
public class MyLogGatewayFilter implements GlobalFilter, Ordered {
//过滤器执行事件
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange);
}
//过滤器放的位置,数值越小越靠前
@Override
public int getOrder() {
return 0;
}
}
ServerWebExchange存储了Request和Response对象和一些拓展方法。
为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置
也称为分布式配置中心,是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口
通过制定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取加载配置信息,配置服务器。默认采用git存储配置信息,并可以通过git客户端方便管理和访问配置内容
github新建仓库,复制git地址(git@github.com:LoliWolf/ShopPlatform.git),本地建git仓库并clone
新建Module,即为配置中心模块cloudConfig Center
pom配置
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
yml配置
spring:
application:
name: eureka0 #注册进eureka服务器的微服务名
cloud:
config:
server:
git:
uri: git@github.com:LoliWolf/ShopPlatform.git
#搜索目录
search-paths: ShopPlatform
#读取分支
label: master
配置读取规则
上步设置了默认分支为master
/{label(分支)}/{application(配置文件名)}-{profile(发布版本)}.yml
http://config-3344.com:3344/master/comfig-prod.yml
http://config-3344.com:3344/master/comfig-test.yml
/{application}-{profile}.yml
使用设置的默认分支
/{application}/{profile}[/{label}]
创客户端Moudle,添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
新建bootstrap.yml配置文件
bootstrap.yml配置文件优先级更高,优先加载,不会被本地配置覆盖
bootstrap.yml配置
spring:
application:
name: config-client #eureka注册的名字
cloud:
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #后缀名称
uri: http://localhost:3344 #配置中心地址,多个用逗号分割
即可在启动微服务时获取到配置
手动动态刷新客户端配置
客户端moudle加监控依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
暴露监控端口
management:
endpoints:
web:
exposure:
include: "*"
在需要配置动态刷新配置的类上添加@RefreshScope注解
对配置客户端/actuator/refresh发POST请求,刷新配置
Eureka是服务注册与发现的组件
在一个分布式系统中,一致性(Consistency),可用性(Availability),分区容错性(Partition tolerance)三个要素最多只能同时实现两点,不可能三者兼顾。
CP:数据是一致的但是如果有个节点挂了,整个服务在几分钟内不能提供服务
AP:数据可能是不一致的,但有节点挂了服务依然可用
eureka遵循AP,zookeeper遵循CP
注册中心:可以让别人注册,也可以注册自己
建项目,pom加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
yml配置
server:
port: 8761 #Eureka服务器默认端口
spring:
application:
name: eureka-server #应用名称
启动类添加注解@EnableEurekaServer
模块pom加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
yml配置
server:
port: 8080
spring:
application:
name: eureka-client-a #要在server注册的服务名
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka #eureka-server地址
启动类加注解@EnableEurekaClient/@EnableDiscoveryClient
同一服务可以注册多个实例
把一个服务启动配置复制一份在program arguments里加--server.port=即可
eureka配置分服务器(server),客户端(client),实例(instance)
服务端需要配置server,instance,客户端需要配置client,instance
# 是否允许开启自我保护模式,缺省:true
# 当Eureka服务器在短时间内丢失过多客户端时,自我保护模式可使服务端不再删除失去连接的客户端
eureka.server.enable-self-preservation = false
# Peer节点更新间隔,单位:毫秒
eureka.server.peer-eureka-nodes-update-interval-ms =
# Eureka服务器清理无效节点的时间间隔,单位:毫秒,缺省:60000,即60秒
eureka.server.eviction-interval-timer-in-ms = 60000
# 服务名,默认取 spring.application.name 配置值,如果没有则为 unknown
eureka.instance.appname = eureka-client
# 实例ID
eureka.instance.instance-id = eureka-client-instance1
# 应用实例主机名
eureka.instance.hostname = localhost
# 客户端在注册时使用自己的IP而不是主机名,缺省:false
eureka.instance.prefer-ip-address = false
# 应用实例IP
eureka.instance.ip-address = 127.0.0.1
# 服务失效时间,失效的服务将被剔除。单位:秒,默认:90
eureka.instance.lease-expiration-duration-in-seconds = 90
# 服务续约(心跳)频率,单位:秒,缺省30
eureka.instance.lease-renewal-interval-in-seconds = 30
# 状态页面的URL,相对路径,默认使用 HTTP 访问,如需使用 HTTPS则要使用绝对路径配置,缺省:/info
eureka.instance.status-page-url-path = /info
# 健康检查页面的URL,相对路径,默认使用 HTTP 访问,如需使用 HTTPS则要使用绝对路径配置,缺省:/health
eureka.instance.health-check-url-path = /health
# Eureka服务器的地址,类型为HashMap,缺省的Key为 defaultZone;缺省的Value为 http://localhost:8761/eureka
# 如果服务注册中心为高可用集群时,多个注册中心地址以逗号分隔。
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka
# 是否向注册中心注册自己,缺省:true
# 一般情况下,Eureka服务端是不需要再注册自己的
eureka.client.register-with-eureka = true
# 是否从Eureka获取注册信息,缺省:true
# 一般情况下,Eureka服务端是不需要的
eureka.client.fetch-registry = true
# 客户端拉取服务注册信息间隔,单位:秒,缺省:30
eureka.client.registry-fetch-interval-seconds = 30
# 是否启用客户端健康检查
eureka.client.health-check.enabled = true
# 获取集群中最新的server节点数据频率 单位:秒 缺省:5min
eureka.client.eureka-service-url-poll-interval-seconds = 60
# 连接Eureka服务器的超时时间,单位:秒,缺省:5
eureka.client.eureka-server-connect-timeout-seconds = 5
# 从Eureka服务器读取信息的超时时间,单位:秒,缺省:8
eureka.client.eureka-server-read-timeout-seconds = 8
# 获取实例时是否只保留状态为 UP 的实例,缺省:true
eureka.client.filter-only-up-instances = true
# Eureka服务端连接空闲时的关闭时间,单位:秒,缺省:30
eureka.client.eureka-connection-idle-timeout-seconds = 30
# 从Eureka客户端到所有Eureka服务端的连接总数,缺省:200
eureka.client.eureka-server-total-connections = 200
# 从Eureka客户端到每个Eureka服务主机的连接总数,缺省:50
eureka.client.eureka-server-total-connections-per-host = 50
eureka采用去中心化模式,在集群中对数据进行广播和扩散
配置各个注册中心实例注册自己,将其他注册中心实例地址添加到配置中
client:
service-url:
defaultZone: http://192.168.12.3:8761/eureka,http://192.168.14.4:8761/eureka
register-with-eureka: true
实例主机名如果相同,如都是localhost,不会被认为是集群
在客户端添加一个注册中心地址即可在注册中心集群广播扩散
不配置主机名
把所有注册中心实例地址全部注册进所有注册中心的client.service-url(注意改端口),启动时候用参数修改端口
客户端也注册所有注册中心实例地址
即通过服务应用名,找到具体服务信息
可在代码中注入DiscoveryClient,获取服务列表
加依赖,启动类加注解 @EnableDiscoveryClient
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
配置文件
spring:
cloud:
zookeeper:
# zk地址
connect-string: localhost:2181
# 会话过期时间
session-timeout: 60000
# 连接超时时间
connection-timeout: 15000
# 初始重试等待时间
base-sleep-time-ms: 1000
# 最大重试次数
max-retries: 10
# 发现客户端
discovery:
enabled: true
加依赖加注解@EnableDiscoveryClient
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
配置
spring:
application:
name: conusle-provider
cloud:
consul:
host: localhost
port: 8500
# 服务发现配置
discovery:
# 启用服务发现
enabled: true
# 启用服务注册
register: true
# 服务停止时取消注册
deregister: true
# 表示注册时使用IP而不是hostname
prefer-ip-address: true
ip-address: 192.168.10.132
# 执行监控检查的频率
health-check-interval: 30s
# 设置健康检查失败多长时间后,取消注册
health-check-critical-timeout: 30s
# 健康检查的路径
health-check-path: /actuator/info
# 服务注册标识,格式为:应用名称+服务器IP+端口
instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
# 服务名
service-name: ${spring.application.name}
@Autowired
private DiscoveryClient discoveryClient;
public String serviceUrl() {
List<ServiceInstance> list = discoveryClient.getInstances("STORES");
if (list != null && list.size() > 0 ) {
return list.get(0).getUri().toString();
}
return null;
}
Spring Cloud 2020.0.0版本彻底删除掉了Netflix除Eureka外的所有组件。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-ribbon</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
Ribbon默认使用Eureka服务发现,在删除Netflix组件后SpringCloud使用Loadbalancer代替
Ribbon分为全局配置和客户端配置,如果添加客户端配置可以在ribbon上级添加application name
# 全局配置
ribbon:
ConnectTimeout: 1000 #服务请求连接超时时间(毫秒)
ReadTimeout: 3000 #服务请求处理超时时间(毫秒)
OkToRetryOnAllOperations: true #对超时请求启用重试机制
MaxAutoRetriesNextServer: 1 #切换重试实例的最大个数
MaxAutoRetries: 1 # 切换实例后重试最大次数
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改负载均衡算法
# 局部配置
user-service:
ribbon:
负载均衡策略的选择:
服务发现和注册自动完成,直接用[RestTemplate](../SpringBoot/SpringBoot.md# 使用RestTemplate发请求)调服务名就能实现负载均衡
uri用"http://"+ {application_name} +"服务接口地址"
配置文件配置服务地址
CLIENT:
ribbon:
# 不使用eureka服务发现
eureka:
enabled: false
# 服务列表
listOfServers: localhost:8001,localhost:8002
# 服务列表刷新间隔
ServerListRefreshInterval: 5000
# 负载策略
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
# Ping策略
NFLoadBalancerPingClassName: com.netflix.loadbalancer.PingUrl
# 设置它的服务实例信息来自配置文件, 如果不设置NIWSServerListClassName就会去euereka里面找
NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
spring:
cloud:
loadbalancer:
retry:
enable: true
如果针对不同的服务使用各自的配置,则可选择在配置类中配置实例选择器
public class CustomLoadBalancerConfiguration {
//实例选择器配置
@Bean
public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(ConfigurableApplicationContext context){
return ServiceInstanceListSupplier.builder()
//使用服务发现
.withDiscoveryClient()
//使用健康检查
.withHealthChecks()
//···
.build(context);
}
}
不同服务的配置:
spring:
cloud:
loadbalancer:
clients:
my-service:
默认使用轮询,内置随机RandomLoadBalancer,轮询RoundRobinLoadBalancer。
新增配置类,不要加@Configuration
public class CustomLoadBalancerConfiguration {
@Bean
ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}
添加配置类,手动选择配置
@Configuration
//value的值指定了在给定的loadbalancer client配置下,要发送请求到哪个服务(服务名)
@LoadBalancerClient(value = "stores", configuration = CustomLoadBalancerConfiguration.class)
public class MyConfiguration {
@Bean
@LoadBalanced
public WebClient.Builder loadBalancedWebClientBuilder() {
return WebClient.builder();
}
}
//配置多个
//@LoadBalancerClients({
// @LoadBalancerClient(value = "stores", configuration = StoresLoadBalancerClientConfiguration.class),
// @LoadBalancerClient(value = "customers", configuration = CustomersLoadBalancerClientConfiguration.class)
//})
仿照官方的RandomLoadBalancer,修改getInstanceResponse()进行实现
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import reactor.core.publisher.Mono;
public class RandomLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private static final Log log = LogFactory.getLog(RandomLoadBalancer.class);
private final String serviceId;
private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
public RandomLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
}
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier)this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next().map((serviceInstances) -> {
return this.processInstanceResponse(supplier, serviceInstances);
});
}
private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {
Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback)supplier).selectedServiceInstance((ServiceInstance)serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + this.serviceId);
}
return new EmptyResponse();
} else { //此处实现自己的负载均衡策略
int index = ThreadLocalRandom.current().nextInt(instances.size());
ServiceInstance instance = (ServiceInstance)instances.get(index);
return new DefaultResponse(instance);
}
}
}
配合[RestTemplate/WebClient](../SpringBoot/SpringBoot.md# 使用RestTemplate发请求),做配置类Bean并在使用时注入
@Bean
@LoadBalanced
public WebClient.Builder loadBalancedWebClientBuilder() {
return WebClient.builder();
}
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
如果已使用服务发现,通常不需要添加这种健康检查机制,因为我们直接从服务注册中心检索实例的健康状态。
spring:
cloud:
loadbalancer:
health-check:
# 创建延迟
initial-delay: 10s
# 检查间隔
interval: 100s
# 健康检查使用的url
path:
default: /actuator/health
service-a: /health
service-b: /health
# 健康检查使用的端口,未指定则为服务实例上的可用端口
port: 8081
# 服务列表的刷新一般由代理实现,例如服务发现。如果不想使用代理来刷新服务列表,可以使用以下配置使服务列表的刷新由HealthCheckServiceInstanceListSupplier操作。
# 开启刷新服务列表
refetch-instances: true
# 设置刷新间隔
refetch-instances-interval: 100s
# 取消额外的健康检查(每个服务实例的刷新也会触发一次健康检查)
repeat-health-check: false
配置选择器:
.withSameInstancePreference()
全局默认:
spring:
cloud:
loadbalancer:
configurations: same-instance-preference
配置选择器:
.withRequestBasedStickySession()
全局默认:
spring:
cloud:
loadbalancer:
configurations: request-based-sticky-session
sticky-session:
# 是否在发送请求前更新服务所选实例(如果原始请求cookie中的服务实例不可用,则该服务实例可能与原始请求cookie中的服务实例不同)
add-service-instance-cookie: true
# 自定义cookie名字,默认为 sc-lb-instance-id
instance-id-cookie-name: sc-lb-instance-id
由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。内嵌Ribbon实现负载均衡。新版202x的openfeign整合移除了Ribbon,改用LoadBalancer
springboot版本不大于2.3.X,使用springcloud版本不大于Hoxton,服务发现使用eureka
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
启动类加注解@EnableFeignClients开启功能
服务发现配置
# 消费者可不注册进Eureka
eureka:
client:
register-with-eureka: false
# Eureka服务端地址
service-url:
defaultZone: http://127.0.0.1:8001/eureka,http://localhost:8002/eureka
@Service
// 上下文标识,服务提供者的微服务应用名
@FeignClient(contextId = "service-name",value = "spring-application-name")
public interface TestService {
// 服务提供者的服务调用地址
@GetMapping("/provider/test/{id}", headers = {"Content-Type=application/json;charset=UTF-8",···})
Object testService(@PathVariable("id") Long id);
}
@FeignClient详细参数:
value/name:服务提供者的应用名
contextId:被用作Bean名称,做配置时可以用;如果多个service都用了同一个服务提供者,value重复,则会因为Bean名称冲突报错,解决方案一:添加配置spring.main.allow-bean-definition-overriding=true,解决方案二:指定不同的contextId作为Bean名字(获取Bean名字规则:contextId>value>name);
url:手动指定feign调用地址,一般用在调试,也可以在配置文件中设置feign.client.config.testClient.url。这么提供url无法使用负载均衡
path:当前FeignClient访问接口的统一前缀,效果等同控制器类上加RequestMapping
decode404:调用发生404时,若该参数为true,则执行decoder解码,否则抛异常
fallback:定义容错处理类,如果抛异常了则执行,该类必须实现。无法知道熔断异常信息
@Component
public class UserRemoteClientFallback implements UserRemoteClient {
@Override
public User getUser(int id) {
return new User(0, "默认fallback");
}
}
@FeignClient(value = "optimization-user", fallback = UserRemoteClientFallback.class)
public interface UserRemoteClient {
@GetMapping("/user/get")
public User getUser(@RequestParam("id")int id);
}
fallbackFactory:同上,可以知道熔断异常信息
@Component
public class UserRemoteClientFallbackFactory implements FallbackFactory<UserRemoteClient> {
private Logger logger = LoggerFactory.getLogger(UserRemoteClientFallbackFactory.class);
@Override
public UserRemoteClient create(Throwable cause) {
return new UserRemoteClient() {
@Override
public User getUser(int id) {
logger.error("UserRemoteClient.getUser异常", cause);
return new User(0, "默认");
}
};
}
}
@FeignClient(value = "optimization-user", fallbackFactory = UserRemoteClientFallbackFactory.class)
public interface UserRemoteClient {
@GetMapping("/user/get")
public User getUser(@RequestParam("id")int id);
}
primary:是否将该FeignClient作为主bean(默认true)。若FeignClient有多个相同的Bean在Spring容器中,当我们在使用@Autowired进行注入的时候,不知道注入哪个,此时需要声明。
qualifier:如果相同的Bean的primary都是false,autowired会不知道注入哪个,此时用qualifier声明
// Feign Client定义
@FeignClient(name = "optimization-user", path="user", qualifier="userRemoteClient")
public interface UserRemoteClient {
@GetMapping("/get")
public User getUser(@RequestParam("id") int id);
}
// Feign Client注入
@Autowired
@Qualifier("userRemoteClient")
private UserRemoteClient userRemoteClient;
configuration:自定义FeignClient配置类,此类配置类不需要加@Configuration
# 使用feign做单/全局服务配置 service-name即服务里设置的上下文标识
feign:
client:
config:
service-name/default:
# 请求处理超时时间
read-timeout: 6000
# 连接超时时间
connect-timeout: 1000
# 使用ribbon做全局配置(旧版)
ribbon:
ReadTimeout: 6000
ConnectTimeout: 5000
日志等级:
NONE:默认的,不显示任何日志;
BASIC:仅记录请求方法、URL、响应状态码及执行时间;
HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息
FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据
单个服务日志记录
logging:
level:
com.demo.feign.FeignDemo: debug
# 下面的配置,也可以做配置类代替
# @Bean
# public Logger.Level level() { return Logger.Level.FULL; }
feign:
client:
config:
default:
loggerLevel: full
全局日志记录
logging:
level:
feign.Logger: debug
# 下面的配置,也可以做配置类代替
# @Bean
# public Logger.Level level() { return Logger.Level.FULL; }
feign:
client:
config:
default:
loggerLevel: full
@PostMapping(value = "/card")
public CardVo createCard(@RequestBody CardDto condition, @RequestHeader MultiValueMap<String, String> headers);
@PostMapping(value = "/book/api", headers = {"Content-Type=application/json;charset=UTF-8", "App-Secret=${app.secret}"})
void saveBook(@RequestBody BookDto condition);
做拦截器实现RequestInterceptor接口
feign.client.config.service-name.request-interceptors=com.demo.interceptor.CustomInterceptor
如果开启@EnableCaching,则会给CachingCapability注册一个bean,使feign客户端识别接口上的注解
@GetMapping("/demo/{filterParam}")
@Cacheable(cacheNames = "demo-cache", key = "#keyParam")
String demoEndpoint(String keyParam, @PathVariable String filterParam);
将OpenFeign抽离出来做一个module,直接pom引包使用
于springcloud2020移除
降级指系统将某些业务或者接口的功能降低,可以是只提供部分功能,也可以是完全停掉所有功能。
主启动类开启@EnableHystrix
在service方法上添加降级配置,降级方法参数要对应
@HystrixCommand(fallbackMethod = "paymentTimeOutHandler",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000")
})
//超时1000ms
public String paymentTimeOutHandler(Integer id){
return "TimeOut";
}
由于使用了openfeign,没有service实体类,将降级配置换到controller上,同上
直接注解在类上
@DefaultProperties(defaultFallback = "defaultExceptionHandler",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000")})
在要加降级的方法上直接加@HystrixCommand
做feign服务的实现类作为降级处理类
@FeignClient(value = "PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentServiceFallback.class)
feign:
hystrix:
enabled: true
client:
config:
default:
read-timeout: 5000
connect-timeout: 1000
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000
当服务发生错误到达一定频次,可以触发熔断,请求不再打到下级微服务,直接调fallback处理并返回
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),//开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),//触发所需请求数量下限
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),//服务错误比例
@HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000")//断路器开启时长
hystrix:
command:
default:
circuitBreaker:
enabled: true
errorThresholdPercentage: 50
requestVolumeThreshold: 3
sleepWindowInMilliseconds: 8000
设计思想:利用消息总线触发一个服务端ConfigServer的/actuator/busrefresh端点,而刷新所有客户端的配置
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
spring: # rabbitmq配置
rabbitmq:
host:
port:
username:
password:
management:
endpoints:
web:
exposure:
include: "busrefresh" # 暴露/actuator/busrefresh端点
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
mq配置添加在bootstrap
spring: # rabbitmq配置
rabbitmq:
host:
port:
username:
password:
在路径中设置目标实例/busrefresh/{destination}
destination默认是spring.application.name:server.port,也可以用spring.cloud.bus.id单独配置
端口号换来选中所有同名的服务
屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型
stream通过Binder对象与消息中间件交互
Binder是应用与消息中间件之间的封装,目前实行了Kafka和RabbitMQ的Binder,通过Binder可以很方便的连接中间件,可以动态的改变消息类型(对应于Kafka的topic,Rab bitMQ的exchange),这些都可以通过配置文件来实现
Channel:队列Queue的抽象,实现存储和转发的媒介,通过Channel对队列进行配置
Source/Sink:发送者/接收者
spring:
application:
name: stream-provider
cloud:
stream:
binders: #要绑定的rabbitmq服务信息
rabbit: #自定义的Binder名称
type: rabbit #binder类型
environment: #环境配置
spring:
rabbitmq:
host: wolf.bj.cn
port: 5672
username: admin
password: 8a57gw72uri
bindings: # 服务整合处理
output: # 自定义通道名称
destination: Exchange #消息目标的交换机名
content-type: application/json #消息类型(json)
binder: rabbit #指定该通道使用的binder
output2: # 自定义通道名称
destination: Exchange2 #消息目标的交换机名
content-type: text/plain #消息类型(文本)
binder: rabbit #指定该通道使用的binder
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
import com.demo.service.IMessageProvider;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import javax.annotation.Resource;
@EnableBinding(Source.class) // 定义消息的输出管道
public class MessageProviderImpl implements IMessageProvider {
@Resource
private MessageChannel output; // 消息发送管道
@Override
public String send() {
output.send(MessageBuilder.withPayload("消息").build());
return null; }
}